/////////////// PARSER

function ParsingException(msg) {
  this.toString = function() {
    return msg;
  };
}

// Nestable Nodes

var AtomicNode = Object.subClass({
  init: function(val) {
    this.val = val;
  },
  toString: function() {
    return this.val.toString();
  }
});

var NameNode = AtomicNode.subClass({
  init: function(val) {
    this._super(val);
  }
});

var NumberNode = AtomicNode.subClass({
  init: function(val) {
    this._super(val);
  },
  
  toString: function() {
    if (Math.floor(this.val) === this.val) {
      return Math.floor(this.val).toString();
    }
    else {
      return this.val.toString();
    }
  },
  
  toBooleanValue: function() {
    if (this.val === 0) {
      return false;
    }
    else if (this.val === 1) {
      return true;
    }
    else {
      throw new RuntimeException("Cannot convert number <" + this.val + "> to boolean");
    }
  },
  
  toNumberValue: function () {
    return this.val;
  }
});

function BlockNode(nodesArray) {
  this.toString = function() {
    if (nodesArray.length > 0) {
      var stringList = nodesArray.mapMethod("toString");
      return "[" + stringList.join(" ") + "]";
    }
    else
    {
      return "[]";
    }
  };
  
  this.concatenatedWith = function(otherBlockNode) {
    return new BlockNode(nodesArray.concat(otherBlockNode.nodes));
  };
  
  this.nodes = nodesArray;
}

// Statement Nodes
function PhraseStatementNode(nodesArray) {
  this.toString = function() {
    var stringList = nodesArray.mapMethod("toString");
    return stringList.join(" ");
  };
  
  this.nodes = nodesArray;
}
function DefinitionStatementNode(nameString, bodyPhraseNode) {
  this.toString = function() {
    return nameString + " = " + bodyPhraseNode.toString();
  };
  
  this.name = nameString;
  this.body = bodyPhraseNode;
}

// Program Nodes
function ProgramNode(statementsArray) {
  this.toString = function() {
    var stringList = statementsArray.mapMethod("toString");
    return stringList.join("; ");
  };
  
  this.statements = statementsArray;
}

// Parser Class
function Parser() {
  
  this.parse = function(lexer) {
  
    var statements = [];

    while (lexer.getCurrent() !== null) {
      statements.push(this.parseStatement(lexer));
    }

    return (new ProgramNode(statements));
  };
  
  this.parseStatement = function(lexer) {
    
    var first = lexer.getCurrent();
    
    if (first && first.getType() === "name") {
      lexer.moveNext();
      var second = lexer.getCurrent();
      
      if (second && second.getType() === "equals") {
        lexer.movePrev();
        return parseDefinitionStatement(lexer);
      }
      else {
        lexer.movePrev();
        return parsePhraseStatement(lexer);
      }      
    }
    else {
      return parsePhraseStatement(lexer);    
    }
    
  };

  function parsePhraseStatement(lexer) {
    var nestableNodes = [];
    var shouldContinuePhrase = true;

    while (lexer.getCurrent() !== null && shouldContinuePhrase) {
      
      var first = lexer.getCurrent();
    
      if (first && first.getType() === "semiColon") {
        lexer.moveNext();
        shouldContinuePhrase = false;
      }
      else {
        nestableNodes.push(parseNestableNode(lexer));
        lexer.moveNext();
      }
    }

    return new PhraseStatementNode(nestableNodes);
  }
  
  function parseDefinitionStatement(lexer) {

    var first = lexer.getCurrent();

    if (first && first.getType() === "name") {
      var definitionName = lexer.getCurrent().getRepr();
      lexer.moveNext();
      ensureAndSkip(lexer, "equals", "=");
      return new DefinitionStatementNode(definitionName, parsePhraseStatement(lexer));
    }
    else {
      throw new ParsingException("Unexpected token in definition: " + lexer.getCurrent().getRepr());
    }
  }
  
  function parseNestableNode(lexer) {
  
    var first = lexer.getCurrent();
  
    if (first && first.getType() === "openBlock") {
      return parseBlock(lexer);
    }
    else if (first && first.getType() === "name") {
      return new NameNode(lexer.getCurrent().getRepr());
    }
    else if (first && first.getType() === "number") {
      return new NumberNode(lexer.getCurrent().getVal());
    }
    else if (first && first.getType() === "closeBlock") {
      throw new ParsingException("Unbalanced brackets");
    }
    else if (first && first.getType() === "semiColon") {
      throw new ParsingException("Unexpected end of statement");
    }
    else if (first && first.getType() === "equals") {
      throw new ParsingException("Only a name can appear before '=' in a definition");
    }
    else {
      throw new ParsingException("Unexpected token: " + lexer.getCurrent().getRepr());
    }
    
  }

  function parseBlock(lexer) {
  
    lexer.moveNext();

    var nestableNodes = [];
    var continueParsingBlock = true;

    while (lexer.getCurrent() !== null && continueParsingBlock) {
      var first = lexer.getCurrent();
    
      if (first && first.getType() === "closeBlock") {
        continueParsingBlock = false;
      }
      else {
        nestableNodes.push(parseNestableNode(lexer));
        lexer.moveNext();
      }
    }

    return new BlockNode(nestableNodes);
  }

  function ensureAndSkip(lexer,expectedType,expectedRepr) {
  
    var first = lexer.getCurrent();
  
    if (first && first.getType() === expectedType) {
      lexer.moveNext();
    }
    else {
      throw new ParsingException("Unexpected token. Expected: " + expectedRepr + "; Actual: " + lexer.getCurrent().getRepr());
    }

  }  
  
}
